/*-
 * ========================LICENSE_START=================================
 * restheart-commons
 * %%
 * Copyright (C) 2019 - 2025 SoftInstigate
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * =========================LICENSE_END==================================
 */
package org.restheart.exchange;

import io.undertow.server.HttpServerExchange;
import io.undertow.util.AttachmentKey;

/**
 * Base class for Response implementations that handle service-based HTTP responses.
 * <p>
 * This abstract class extends Response to provide specialized functionality for responses
 * that are generated by RESTHeart services (as opposed to proxy or static resource handlers).
 * It manages content generation, custom response sending, and attachment to the HTTP exchange
 * with strict lifecycle management to ensure only one response object exists per request.
 * </p>
 * <p>
 * ServiceResponse is the foundation for all service-specific response types including:
 * <ul>
 *   <li>MongoResponse for MongoDB operation results</li>
 *   <li>GraphQLResponse for GraphQL query results</li>
 *   <li>BsonResponse for BSON content responses</li>
 *   <li>JsonResponse for JSON content responses</li>
 *   <li>Custom service responses for specific business logic</li>
 * </ul>
 * </p>
 * <p>
 * Key features:
 * <ul>
 *   <li>Content management and string serialization</li>
 *   <li>Single instance per exchange enforcement</li>
 *   <li>Custom response sender support for specialized delivery</li>
 *   <li>Error response handling and formatting</li>
 *   <li>Integration with service processing pipeline</li>
 * </ul>
 * </p>
 * <p>
 * <strong>Lifecycle:</strong> Only one response object can be instantiated per request.
 * The response object is instantiated by ServiceExchangeInitializer using the
 * responseInitializer() function defined by the handling service.
 * </p>
 *
 * @author Andrea Di Cesare {@literal <andrea@softinstigate.com>}
 * @param <T> the type of content handled by this service response
 */
public abstract class ServiceResponse<T> extends Response<T> {
    /** Attachment key for storing the service response instance in the HTTP exchange. */
    private static final AttachmentKey<ServiceResponse<?>> RESPONSE_KEY = AttachmentKey.create(ServiceResponse.class);

    /** The content of the response. */
    protected T content;
    
    /** Optional custom sender for specialized response delivery. */
    private Runnable customSender = null;

    /**
     * Constructs a new ServiceResponse and attaches it to the HTTP exchange.
     * <p>
     * This constructor creates a service response and automatically attaches it
     * to the exchange using the internal RESPONSE_KEY. This ensures that the
     * response can be retrieved later using the static factory methods.
     * </p>
     *
     * @param exchange the HTTP server exchange to wrap and attach to
     * @throws IllegalStateException if another ServiceResponse is already attached to the exchange
     */
    protected ServiceResponse(HttpServerExchange exchange) {
        this(exchange, false);
    }

    /**
     * Constructs a new ServiceResponse with optional exchange attachment.
     * <p>
     * This constructor provides control over whether the response instance is attached
     * to the exchange. When attached (dontAttach=false), the response can be retrieved
     * using static factory methods. When not attached (dontAttach=true), the response
     * exists independently and must be managed manually.
     * </p>
     * <p>
     * An initialized response is normally attached to the exchange using the RESPONSE_KEY
     * to enable retrieval and ensure single-instance semantics per exchange.
     * </p>
     *
     * @param exchange the HTTP server exchange to wrap
     * @param dontAttach true if the response should not be attached to the exchange, false otherwise
     * @throws IllegalStateException if dontAttach is false and another ServiceResponse is already attached
     */
    protected ServiceResponse(HttpServerExchange exchange, boolean dontAttach) {
        super(exchange);

        if (!dontAttach) {
            if (exchange.getAttachment(RESPONSE_KEY) != null) {
                throw new IllegalStateException("Error instantiating response object "
                        + getClass().getSimpleName()
                        + ", "
                        + exchange.getAttachment(RESPONSE_KEY).getClass().getSimpleName()
                        + " already bound to the exchange");
            }

            exchange.putAttachment(RESPONSE_KEY, this);
     }
    }

    /**
     * Retrieves the ServiceResponse instance attached to the given HTTP exchange.
     * <p>
     * This factory method returns the ServiceResponse that was previously attached
     * to the exchange during initialization. It ensures that service handlers can
     * access the response object without needing to pass it explicitly through
     * the processing pipeline.
     * </p>
     *
     * @param exchange the HTTP server exchange to retrieve the response from
     * @return the ServiceResponse attached to the exchange
     * @throws IllegalStateException if no ServiceResponse has been attached to the exchange
     */
    public static ServiceResponse<?> of(HttpServerExchange exchange) {
        var ret = exchange.getAttachment(RESPONSE_KEY);

        if (ret == null) {
            throw new IllegalStateException("Response not initialized");
        }

        return ret;
    }

    /**
     * Retrieves the ServiceResponse instance attached to the exchange, cast to the specified type.
     * <p>
     * This type-safe factory method returns the ServiceResponse attached to the exchange
     * and casts it to the requested type. It provides compile-time type safety while
     * performing runtime type checking to ensure the attached response is of the expected type.
     * </p>
     *
     * @param <R> the expected ServiceResponse subtype
     * @param exchange the HTTP server exchange to retrieve the response from
     * @param type the expected class type of the ServiceResponse
     * @return the ServiceResponse attached to the exchange, cast to type R
     * @throws IllegalStateException if no ServiceResponse is attached or if the attached response is not of the expected type
     */
    @SuppressWarnings("unchecked")
    public static <R extends ServiceResponse<?>> R of(HttpServerExchange exchange, Class<R> type) {
        var ret = exchange.getAttachment(RESPONSE_KEY);

        if (ret == null) {
            throw new IllegalStateException("Response not initialized");
        }

        if (type.isAssignableFrom(ret.getClass())) {
            return (R) ret;
        } else {
            throw new IllegalStateException("Response bound to exchange is not "
                    + "of the specified type,"
                    + " expected " + type.getSimpleName()
                    + " got " + ret.getClass().getSimpleName());
        }
    }

    /**
     * Returns the content of the response.
     * <p>
     * This method provides access to the response content that has been set
     * for this service response. The content is typically generated by the
     * service handler and represents the data to be sent back to the client.
     * </p>
     *
     * @return the response content, or null if no content has been set
     */
    public T getContent() {
        return this.content;
    }

    /**
     * Sets the content of the response.
     * <p>
     * This method allows setting the response content that will be sent back
     * to the client. The content should be of the appropriate type for the
     * specific service response implementation.
     * </p>
     *
     * @param content the content to set for this response
     */
    public void setContent(T content) {
        this.content = content;
    }

    /**
     * Reads the content as a String for transmission to the client.
     * <p>
     * This abstract method must be implemented by subclasses to convert their
     * specific content type T into a string representation suitable for HTTP
     * transmission. The method is used by ResponseSender to generate the final
     * response content that will be sent to the client.
     * </p>
     * <p>
     * Implementations should handle:
     * <ul>
     *   <li>Proper serialization of the content type to string format</li>
     *   <li>Character encoding considerations (typically UTF-8)</li>
     *   <li>Null content scenarios</li>
     *   <li>Error handling during content conversion</li>
     * </ul>
     * </p>
     *
     * @return the content as a string ready for HTTP transmission, or null if no content is available
     */
    public abstract String readContent();

    /**
     * Sets a custom sender for specialized response delivery.
     * <p>
     * If a customSender is set (not null), the ResponseSender handler will
     * delegate to customSender.run() the responsibility to send the response
     * content to the client. This allows for specialized response delivery
     * mechanisms such as streaming, chunked transfer, or custom protocols.
     * </p>
     * <p>
     * Common use cases for custom senders include:
     * <ul>
     *   <li>Streaming large files or data sets</li>
     *   <li>Server-sent events (SSE) implementations</li>
     *   <li>WebSocket upgrade responses</li>
     *   <li>Custom binary protocol responses</li>
     *   <li>Compressed or encoded content delivery</li>
     * </ul>
     * </p>
     *
     * @param customSender the custom sender implementation, or null to use default response sending
     */
    public void setCustomSender(Runnable customSender) {
        this.customSender = customSender;
    }

    /**
     * Returns the custom sender for this response.
     * <p>
     * This method returns the custom sender that was previously set via
     * {@link #setCustomSender(Runnable)}. If no custom sender has been set,
     * returns null, indicating that the default response sending mechanism
     * should be used.
     * </p>
     *
     * @return the custom sender implementation, or null if using default response sending
     */
    public Runnable getCustomSender() {
        return this.customSender;
    }

    /**
     * Sets the response in an error state with service-specific error formatting.
     * <p>
     * This abstract method must be implemented by subclasses to provide error response
     * formatting appropriate for their content type and service context. The method
     * should set the HTTP status code and create a properly formatted error response body.
     * </p>
     * <p>
     * Implementations should handle the error information consistently and provide
     * meaningful error responses that clients can understand and process appropriately.
     * The error response format should be consistent with the service's normal response format.
     * </p>
     *
     * @param code the HTTP status code to set (e.g., 400, 404, 500)
     * @param message the error message to include in the response
     * @param t an optional throwable that caused the error (can be null)
     */
    @Override
    public abstract void setInError(int code, String message, Throwable t);
}
